/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.web.npmpackage.core.npminstall;

import com.inspur.edp.web.common.environment.ExecuteEnvironment;
import com.inspur.edp.web.common.io.FileUtility;
import com.inspur.edp.web.common.logger.WebLogger;
import com.inspur.edp.web.common.utility.CommonUtility;
import com.inspur.edp.web.common.utility.StringUtility;
import com.inspur.edp.web.npmpackage.api.entity.NpmInstallParameter;
import com.inspur.edp.web.npmpackage.api.entity.NpmPackageConstants;
import com.inspur.edp.web.npmpackage.api.entity.NpmPackageResponse;
import com.inspur.edp.web.npmpackage.api.entity.packagejson.NpmPackageJsonInfo;
import com.inspur.edp.web.npmpackage.api.entity.settings.NpmSettings;
import com.inspur.edp.web.npmpackage.api.entity.settings.NpmUpdatePolicy;
import com.inspur.edp.web.npmpackage.core.npminstall.global.NpmInstallGlobalManager;
import com.inspur.edp.web.npmpackage.core.npmlogin.NpmLoginCommandExecutor;
import com.inspur.edp.web.npmpackage.core.npmlogout.NpmLogoutCommandExecutor;
import com.inspur.edp.web.npmpackage.core.npmpackagecheck.NpmPackageCheck;
import com.inspur.edp.web.npmpackage.core.npmsetting.NpmSettingConvertor;
import com.inspur.edp.web.npmpackage.core.npmsetting.NpmSettingManager;

import java.io.File;
import java.util.Arrays;


/**
 * npm 安装manager  进行npm安装的统一入口
 *
 * @author noah
 */
public class NpmInstallManager {
    private static final Object _lockObj = new Object();

    /**
     * 安装前检测是否可以安装
     *
     * @param npmInstallParameter
     * @return
     */
    public static NpmPackageResponse npmInstallCheck(NpmInstallParameter npmInstallParameter) {
        NpmPackageResponse npmPackageResponse = NpmInstallParameterValidator.validate(npmInstallParameter);
        if (!npmPackageResponse.isSuccess()) {
            return npmPackageResponse;
        }
        // 获取node_modules 要安装的路径
        boolean isUpgradeTool = npmInstallParameter.getExecuteEnvironment().equals(ExecuteEnvironment.UpgradeTool);
        String currentWorkPath = FileUtility.getCurrentWorkPath(isUpgradeTool);

        // 获取待执行安装的node_modules 路径
        String parentNode_ModulesPath = NodeModulesPathGenerator.generatePackageJsonPath(npmInstallParameter, currentWorkPath);

        // 设置对应权限，防止由于无权限而导致恩无法写入问题
        FileUtility.setPermission(parentNode_ModulesPath);

        String lockFilePath = NpmInstallLockFilePathGenerator.generate(parentNode_ModulesPath);

        // 根据server启动时间和当前创建时间来进行比较  如果比server创建时间新 那么自动删除
        long serverStartTime = CommonUtility.getServerStartTime();
        File lockFileInfo = new File(lockFilePath);
        boolean needAutoDeleteLockFile = lockFileInfo.exists() && lockFileInfo.lastModified() <= serverStartTime;

        // 避免多线程同时访问造成
        synchronized (_lockObj) {
            if (needAutoDeleteLockFile) {
                FileUtility.deleteFile(lockFilePath);
            } else {
                if (FileUtility.exists(lockFilePath)) {
                    return NpmPackageResponse.createError("正在执行安装操作，请勿重复执行！");
                }
            }
        }
        return NpmPackageResponse.create();
    }

    /**
     * 执行默认的安装操作
     *
     * @param isUpgradeTool
     * @return
     */
    public static NpmPackageResponse npmInstallWithDefault(boolean isUpgradeTool, boolean isRuntime, boolean isGlobal) {
        NpmPackageResponse packageResponse;
        if (!isGlobal) {
            NpmSettings npmSettings = NpmSettingManager.getNpmSetting(isUpgradeTool);
            NpmInstallParameter packageParameter = NpmSettingConvertor.convertFromNpmSetting(npmSettings);
            // 依据参数进行默认目录的安装
            if (isRuntime) {
                packageParameter.setExecuteEnvironment(ExecuteEnvironment.Runtime);
            } else {
                packageParameter.setExecuteEnvironment(ExecuteEnvironment.Design);
            }

            packageResponse = npmInstall(packageParameter, false);
        } else {
            packageResponse = NpmInstallGlobalManager.npmInstallWithDefault(isUpgradeTool);
        }
        if (!packageResponse.isSuccess()) {
            WebLogger.Instance.info(packageResponse.getErrorMessage(), NpmInstallManager.class.getName());
        }
        // 不再返回错误信息  如果遇到安装异常 进行提示 而不输出错误
        return NpmPackageResponse.create();
    }

    /**
     * 执行npm 安装
     *
     * @param npmInstallParameter
     * @return
     */
    public static NpmPackageResponse npmInstall(NpmInstallParameter npmInstallParameter, boolean enableForceUpdate) {
        NpmPackageResponse npmPackageResponse = NpmInstallParameterValidator.validate(npmInstallParameter);
        if (!npmPackageResponse.isSuccess()) {
            return npmPackageResponse;
        }
        if (!enableForceUpdate) {
            if (npmInstallParameter.isOfflineMode()) {
                WebLogger.Instance.info("开启离线模式，自动更新离线包停止", NpmInstallManager.class.getName());
                return npmPackageResponse;
            }
            if (npmInstallParameter.getUpdatePolicy().equals(NpmUpdatePolicy.manual)) {
                WebLogger.Instance.info("离线包更新策略为手工，自动更新离线包停止", NpmInstallManager.class.getName());
                return npmPackageResponse;
            }
            if (npmInstallParameter.getUpdatePolicy().equals(NpmUpdatePolicy.never)) {
                WebLogger.Instance.info("离线包更新策略为Never，自动更新离线包停止", NpmInstallManager.class.getName());
                return npmPackageResponse;
            }
        }

        // 获取node_modules 要安装的路径
        boolean isUpgradeTool = npmInstallParameter.getExecuteEnvironment() == ExecuteEnvironment.UpgradeTool;
        String currentWorkPath = FileUtility.getCurrentWorkPath(isUpgradeTool);

        // 获取待执行安装的node_modules 路径
        String parentNode_ModulesPath = NodeModulesPathGenerator.generatePackageJsonPath(npmInstallParameter, currentWorkPath);

        String lockFilePath = NpmInstallLockFilePathGenerator.generate(parentNode_ModulesPath);

        // 根据server启动时间和当前创建时间来进行比较  如果比server创建时间新 那么自动删除
        boolean reWithUnFinishedInstall = false;
        long serverStartTime = CommonUtility.getServerStartTime();
        File lockFileInfo = new File(lockFilePath);
        boolean needAutoDeleteLockFile = lockFileInfo.exists() && lockFileInfo.lastModified() <= serverStartTime;
        if (needAutoDeleteLockFile) {
            FileUtility.deleteFile(lockFilePath);
            // 继续执行未完成的安装
            reWithUnFinishedInstall = true;
        }

        // 避免多线程同时访问造成
        synchronized (_lockObj) {
            if (FileUtility.exists(lockFilePath)) {
                return NpmPackageResponse.createError("npm 正在安装，若需重新安装，请删除lock文件！");
            }
        }
        String packageJsonPathInServer = PackageJsonPathGenerator.generate(currentWorkPath, npmInstallParameter.isMobile());
        if (!FileUtility.exists(packageJsonPathInServer)) {
            String errorMessage = "server不包含package.json 文件,server path is :" + packageJsonPathInServer;
            WebLogger.Instance.info(errorMessage, NpmInstallManager.class.getName());
            return NpmPackageResponse.create();
        }


        // node_modules 目录下的package.json 文件是否存在
        String packageJsonPathInNodeModules = FileUtility.combine(parentNode_ModulesPath, NpmPackageConstants.PackageJsonName);
        String nodeModulesPath = FileUtility.combine(parentNode_ModulesPath, "node_modules");
        // 如果不是强制安装 只有在package.json 和node_modules 目录均存在的前提下才判断是否无需安装
        if (!reWithUnFinishedInstall && !enableForceUpdate && FileUtility.exists(packageJsonPathInNodeModules) && FileUtility.exists(nodeModulesPath)) {
            String serverPackageJsonContent = FileUtility.readAsString(packageJsonPathInServer);
            NpmPackageJsonInfo serverPackageJsonInfo = NpmPackageCheck.packageJsonInfoGenerate(serverPackageJsonContent);

            String nodeModulesPackageJsonContent = FileUtility.readAsString(packageJsonPathInNodeModules);
            NpmPackageJsonInfo nodeModulesPackageJsonInfo = NpmPackageCheck.packageJsonInfoGenerate(nodeModulesPackageJsonContent);

            if (serverPackageJsonInfo.equals(nodeModulesPackageJsonInfo)) {
                WebLogger.Instance.info("离线包版本未发生变更，无需执行离线包安装", NpmInstallManager.class.getName());
                deleteLockFile(lockFilePath);
                return NpmPackageResponse.create();
            }
        }


        try {

            FileUtility.createFile(lockFilePath);

            // 删除package-lock.json 文件
            deletePackageLockJson(parentNode_ModulesPath);

            // 如果文件目录不存在 那么创建对应的文件目录 拷贝package.json 到目标文件目录
            if (!FileUtility.exists(parentNode_ModulesPath)) {
                FileUtility.createDirectory(parentNode_ModulesPath);
            }
            WebLogger.Instance.info("开始执行npm install 安装", NpmInstallManager.class.getName());

            WebLogger.Instance.info("开始执行npm install 命令", NpmInstallManager.class.getName());


            // 仅在帐号和密码都设置的前提下进行登录操作
            boolean needLogin = !StringUtility.isNullOrEmpty(npmInstallParameter.getUserName()) && !StringUtility.isNullOrEmpty(npmInstallParameter.getPassword());

            if (needLogin) {
                WebLogger.Instance.info("执行npm登录操作", NpmInstallManager.class.getName());
                NpmPackageResponse loginPackageResponse = NpmLoginCommandExecutor.execute(npmInstallParameter, currentWorkPath);
                if (!loginPackageResponse.isSuccess()) {
                    deleteLockFile(lockFilePath);
                    return loginPackageResponse;
                }
            }

            // 执行package.json 文件复制
            NpmPackageResponse packageJsonCopyResponse = PackageJsonCopyManager.copy(npmInstallParameter, currentWorkPath, parentNode_ModulesPath);
            if (!packageJsonCopyResponse.isSuccess()) {
                deleteLockFile(lockFilePath);
                return packageJsonCopyResponse;
            }

            // npm install 命令执行
            NpmPackageResponse installPackageResponse = NpmInstallCommandExecutor.execute(npmInstallParameter, currentWorkPath, parentNode_ModulesPath);
            if (!installPackageResponse.isSuccess()) {
                deleteLockFile(lockFilePath);
                // 如果安装失败  移除版本文件
                FileUtility.deleteFile(packageJsonPathInNodeModules);
                return installPackageResponse;
            }

            if (false) {
                // 仅在需要登录的前提下执行登录退出操作
                WebLogger.Instance.info("执行npm登出操作", NpmInstallManager.class.getName());
                NpmPackageResponse logoutPackgaeResponse = NpmLogoutCommandExecutor.execute(npmInstallParameter, currentWorkPath);
                if (!logoutPackgaeResponse.isSuccess()) {
                    deleteLockFile(lockFilePath);
                    return logoutPackgaeResponse;
                }
            }
        } catch (Exception ex) {
            deleteLockFile(lockFilePath);
        }


        deleteLockFile(lockFilePath);

        WebLogger.Instance.info("npm install 执行完毕", NpmInstallManager.class.getName());
        return NpmPackageResponse.create();
    }

    /**
     * 删除锁定文件
     *
     * @param lockFilePath
     */
    private static void deleteLockFile(String lockFilePath) {
        try {
            if (FileUtility.exists(lockFilePath)) {
                FileUtility.deleteFile(lockFilePath);
            }
        } catch (Exception ignored) {
        }
    }


    /**
     * 删除package-lock.json 文件
     *
     * @param parentNode_ModulesPath
     */
    private static void deletePackageLockJson(String parentNode_ModulesPath) {
        String packageLockJsonPath = FileUtility.combine(parentNode_ModulesPath, NpmPackageConstants.PackageLockJsonName);
        if (FileUtility.exists(packageLockJsonPath)) {
            try {
                FileUtility.deleteFile(packageLockJsonPath);
            } catch (Exception ex) {
                WebLogger.Instance.info("delete package-lock.json failed, the path is " + packageLockJsonPath + " , the stack is " + Arrays.toString(ex.getStackTrace()), NpmInstallManager.class.getName());
            }

        }
    }
}
